<?php
// $Id$

/**
 * @file
 * Main class for classes module.
 */




class Jsonrpc {

	function __construct($uri = NULL) {
		if (isset($uri))
		$this->uri = $uri;
	}

	function get_indexes() { return jsonrpc_get_indexes(); }
	function create_index($x) { return jsonrpc_create_index($x); }
	function drop_index($x) { return jsonrpc_drop_index($x); }


	function save($obj) {
		return jsonrpc_save_object($obj);		
	}

	function load($id) {
		return jsonrpc_load_object($id);
	}


	function set($name, $value) {
		return $value;
	}


	/*
	 * decode(input): decodes input. json_decode() is used by default.
	 */
	function decode($x) {
		return NULL === ($obj = json_decode($x, FALSE)) ? $this->error('Parse error.', -32700) : $obj;
	}


	/**
	 * Encodes output according to 'encode' GET param.
	 *
	 * @param $x
	 *   any data
	 */
	function encode($x) {
		switch ($_GET['format']) {
			default:
				return function_exists('json2_encode') ? json2_encode($x, JSON2_FORMAT) : jsonrpc_json_encode($x);
			case 'classes':
				include_once dirname(__FILE__) . '/json_encode.inc';
				return jsonrpc_json_encode($x);
			case 'json2':
				return json2_encode($x, JSON2_FORMAT);
			case 'php':
				return json_encode($x);
			case 'text':
				if (!isset($x->result))
					return 'ERROR: ' . $x->error->message . ': ' . $x->error->data;
				if (is_string($x->result))
					return $x->result;
				if (isset($x->result['rows'])) {
					$out = array();
					foreach ($x->result['rows'] as $row)
					$out[] = join("\t", $row);
					return join("\n", $out);
				}
				return jsonrpc_json_encode($x);
		}
	}


	/*
	 * run(request body): parses and executes request.
	 */
	function run(&$x = NULL, $q = NULL) {
		$this->query = $q;
		if (is_string($x))
			$req = json_decode($x);
		elseif(is_array($x))
			$req = (object)$x;
		elseif(is_object($x))
			$req = $x;
		else
			$req = (object)NULL;
			$this->init($req);
			$this->invoke();
			//var_dump($this->request);
	
			unset($req->method, $req->params, $req->uri);
	
	
			return $req;
	}

	function run2($a, &$b = NULL) {
		if (is_string($a)) {
			$a = array(
				'classes' => '2.0',
				'method' => $a,
				'id' => 0,
			);
		}
		if (is_array($a)) {
			$x = (object)NULL;
			foreach ($a as $i => &$v)
			$x->$i =& $v;
			$a = $x;
		}
		if (is_object($a)) {
			if (isset($b))
				$a->params =& $b;
			$this->init($a);
			$this->invoke();
			return $this->result;
		}
	}


	/* ------------------------------------------------------------ */
	private $instance = array();

	private function init(&$req = NULL) {
		//var_dump(is_object($req));
		unset($this->cl, $this->method, $this->params);
		if (isset($req)) $this->request =& $req;
		//if (!is_object($req))
		//  return;
		unset($req->result, $req->error);
		$q = array();
		if ($x = $this->query) $q[] = $x;
		if ($x = $req->method) $q[] = $x;
		if (isset($req->param) && !isset($req->params)) {
			$req->params = $req->param;
			unset($req->param);
		}
		$this->q = join('/', $q);
		//var_dump($this->q);
	}


	/*
	 * invoke(json request obj.): executes classes call.
	 */
	private function invoke() {
		global $user;
		$req =& $this->request;
		try {

			if ($req) {
				if (isset($req->uri) && preg_match("/^([^\?]*)(\?(.*))?$/", $req->uri, $match)) {
					$this->q = $match[1];
					parse_str($match[3], $_GET);
				}
			}

			if (isset($req) && !is_object($req))
				throw new Exception("Invalid Request.", -32600);

			if (!preg_match("/^(\w+)[\.\/](\w+)\/?(.*)$/",  $this->q, $match))
				throw new Exception("Invalid Request.", -32600);

			list(, $c, $m, $a) = $match;

			if (!class_exists($c, FALSE))
				jsonrpc_load_class($c);
			if (isset($a) && is_string($a) && FALSE !== strpos($a, ', '))
				$a = explode(', ', $a);
			if (isset($_GET['id']))
				$req->id = $_GET['id'];
			$is_super = $user->uid == 1;
			if ($user->uid == 0 && $_SERVER['REMOTE_ADDR'] == '127.0.0.1' && !isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
				$is_super = TRUE;
				$user->uid = 1;
			}
			if (!method_exists($c, $m))
				throw new Exception('Method does not exists.', -32601);
			if (variable_get('site_offline', 0) && !$is_super)
				throw new Exception('Site if offline');
			$refl = new ReflectionMethod($c, $m);

			//$args = array();

			if (!isset($req->params)) {
				if ($a)
					$args = $a;
				elseif (isset($_GET['params']))
					$args = explode(', ', $_GET['params']);
				elseif (isset($_GET['param']))
					$args = explode(', ', $_GET['param']);
			}
			else
				$args = $req->params;

			$np = $refl->getNumberOfParameters();
			$nr = $refl->getNumberOfRequiredParameters();

			if ($nr <= 1 && !(is_array($args) && count($args) == $np && $np > 1 ) )
				$args = array($args);

			switch (TRUE) {
				case $refl->isStatic():
					$req->result = call_user_func_array(array($c, $m), $args);
					break;
				case $refl->isPublic():
					$inst = $this->instance($c);
					$req->result = call_user_func_array(array($inst, $m), $args);
					break;
				case $refl->isProtected() && $is_super:
					$refl->setAccessible(TRUE);
					$req->result = $refl->invokeArgs(new $c, $args);
					break;
				default:
					throw new Exception("Method not found or permission denied on launching $c::$m.");
			}
		} catch (Exception $e) {
			if (!is_object($req))
			$req = $this->request = (object)NULL;
			$data = isset($e->data) ? $e->data : get_class($e);
			$req->error = array('code' => $e->getCode(), 'message' => $e->getMessage(), 'data' => $data);
		}
	}


	private function result(&$x, $inst = NULL) {
		unset($this->result);
		$req = $this->request;
		if (!empty($req->error)) return;
		$this->result =& $x;
		unset($req->params, $req->method, $req->class);
		if (is_array($x))
			foreach ($x as $k => $v)
				if (is_string($k) && ($k[0]=='#'))
					unset($x[$k]);
		$req->result =& $x;
		$req->classes = '2.0';
		if (!isset($req->id))
			$req->id = $_GET['id'];
		if ($inst)
			if ($inst->session)
				$req->instance = $this->put($inst, $req->instance);
		return $this->out;
	}


	private function error($message, $code = -1, $data = NULL) {
		$req = $this->request;
		$req->error = (object)array('code' => $code, 'message' => $message, 'data' => $data);
		unset($req->params, $req->method, $req->class);
		return $req;
	}


	private function instance($c) {
		if ($c == __CLASS__)
			return $this;
		return isset($this->instance[$c]) ? $this->instance[$c] : $this->instance[$c] = new $c;
	}


	function __destruct() {
		foreach ($this->instance as $c => $x)
			unset($this->instance[$c]);
	}

}




